package com.wufun.overseas.joystick;

import android.animation.ValueAnimator;
import android.content.Context;
import android.os.Handler;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import android.widget.Toast;

import com.papa.controller.core.BaseEvent;
import com.papa.controller.core.ControllerListener;
import com.papa.controller.core.ControllerManager;
import com.papa.controller.core.ControllerMonitor;
import com.papa.controller.core.PadKeyEvent;
import com.papa.controller.core.PadMotionEvent;
import com.papa.controller.core.PadStateEvent;
import com.wufun.overseas.R;
import com.wufun.overseas.joystick.map.BlackListManager;
import com.wufun.overseas.joystick.map.KeyCodes;
import com.wufun.overseas.joystick.map.KeyMap;
import com.wufun.overseas.joystick.map.KeyMapEvent;
import com.wufun.overseas.joystick.map.KeyMapManager;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class KeyMapController {
    public static KeyMapController instance = null;

    private Context mContext;

    List<ControllerManager.ControllerDevice> mGameControllers;

    protected KeyMap.EmuMap emuType;
    protected List<String>  mappedKeyValue;

    protected int[] curInputData = new int[]{0, 0, 0, 0};


    public static KeyMapController getInstance() {
        if (instance == null) {
            instance = new KeyMapController();
        }
        return instance;
    }

    protected KeyMapController() {
        if (this.emuType == null) return;
        init();
    }

    public void register(Context ctx, KeyMap.EmuMap emuMap) {
        this.mContext = ctx;
        this.emuType = emuMap;

        init();
    }

    public void unregister() {
        stopMonitor();
    }

    private void init() {
        try {
            KeyMapManager.load(emuType);

            // init mappedKeyValue
            Object[] objs  = KeyMapManager.getMap(emuType.section).keySet().toArray();
            String[] array = Arrays.asList(objs).toArray(new String[0]);
            if (array == null || array.length == 0) {
                array = emuType.keysKey;
            }
            mappedKeyValue = Arrays.asList(array);

            startMonitor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Deprecated
    public void pullConnectionState() {
        if (mControllerMonitor != null) {
            handleChanged(mControllerMonitor.getControllerDeviceCount() == 1 ? 1 : 0, mControllerMonitor.getControllerDevices());
        } else {
            startMonitor();
        }
    }

    private void handleChanged(int state, List<ControllerManager.ControllerDevice> list) {
        try {
            //show toast
            if ((state == 1) || (state == 0)) {
                if (validDeviceChanged(list))
                    toast(state == 1);
            }

            //save instance
            if (mGameControllers == null)
                mGameControllers = new ArrayList<ControllerManager.ControllerDevice>();
            mGameControllers.clear();
            mGameControllers.addAll(list);


            //virtual keyboard
            boolean hide = KeyMapManager.virtualKeyboard(0);
            int size = getValidDeviceCount(list);
            if (size < 1) {
                hide &= false;
            } else {
                hide &= true;
            }

            //notify emulator
            if (mOnKeyMapListener != null) {
                mOnKeyMapListener.onVirtualKeyboard(hide);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean validDeviceChanged(List<ControllerManager.ControllerDevice> list) {
        boolean ret = false;

        List<ControllerManager.ControllerDevice> temp = new ArrayList<ControllerManager.ControllerDevice>();
        if (list != null)
            temp.addAll(list);

        if (mGameControllers == null) {
            if (temp != null && temp.size() != 0 && getValidDeviceCount(temp) > 0) ret = true;
        } else {
            int currentSize = 0;
            int size = 0;
            if (temp != null) size = temp.size();
            currentSize = mGameControllers.size();
            if (currentSize < size) {
                temp.removeAll(mGameControllers);
                ret = getValidDeviceCount(temp) > 0;
            } else {
                mGameControllers.removeAll(temp);
                ret = getValidDeviceCount(mGameControllers) > 0;
            }
        }

        return ret;
    }

    private void toast(boolean add) {
        if (mContext == null) return;
        if (add) {
            Toast.makeText(mContext, R.string.gamepad_connected, Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(mContext, R.string.gamepad_disconnected, Toast.LENGTH_SHORT).show();
        }
    }

    //region Blacklist
    private int getValidDeviceCount(List<ControllerManager.ControllerDevice> list) {
        return getValidDevice(getValidDevice(list)).size();
    }

    private List<ControllerManager.ControllerDevice> getValidDevice(List<ControllerManager.ControllerDevice> list) {
        List<ControllerManager.ControllerDevice> ret = new ArrayList<ControllerManager.ControllerDevice>();
        if (list == null || list.size() == 0) return ret;

        for (int i = 0; i < list.size(); i++) {
            String deviceName = list.get(i).getName();
            if (!inBlackList(deviceName)) {
                ret.add(list.get(i));
            }
        }
        return ret;
    }

    public boolean inBlackList(String deviceName) {
        return BlackListManager.getInstance().isInBlackList(deviceName);
    }
    //endregion

    //region Keymap handle
    private OnKeyMapListener mOnKeyMapListener = null;

    public void setOnKeyMapListener(OnKeyMapListener l) {
        this.mOnKeyMapListener = l;
    }

    public interface OnKeyMapListener {
        public void onKeyMap(int[] keyState, KeyMapEvent keyMapEvent, KeyEvent event);

        /**
         *
         * @param keyCode 200,LEFT_STICK<br/>
         *                201,RIGHT_STICK
         * @param x
         * @param y
         */
        public void onGenericMotionEvent(int keyCode, float x, float y);

        public void onVirtualKeyboard(boolean hide);
    }

    @Deprecated
    public boolean onKeyDispatcher(KeyEvent event) {

        try {
            if (event.getDevice() != null)
                if (inBlackList(event.getDevice().getName())) return false;

            KeyMapEvent keyMapEvent = KeyMapManager.getKeyMapEvent(0, event);

            processAnalog(event, keyMapEvent);
            processButtons(event, keyMapEvent);

            if (mOnKeyMapListener != null) {
                mOnKeyMapListener.onKeyMap(curInputData, keyMapEvent, event);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }

    private void processAnalog(KeyEvent event, KeyMapEvent mapEvent) {
        if (skipAnalogProcess()) return;

        int state = 0;
        if (isUseDpad1()) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                state = KeyCodes.KEY_LEFT1.value();
            } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                state = KeyCodes.KEY_UP1.value();
            } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                state = KeyCodes.KEY_RIGHT1.value();
            } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                state = KeyCodes.KEY_DOWN1.value();
            }
        } else {
            if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
                state = KeyCodes.KEY_LEFT.value();
            } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP) {
                state = KeyCodes.KEY_UP.value();
            } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
                state = KeyCodes.KEY_RIGHT.value();
            } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
                state = KeyCodes.KEY_DOWN.value();
            }
        }

        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            curInputData[0] |= state;
        } else {
            curInputData[0] &= ~state;
        }

//        Logger.e("process", "current=" + state + " combine=" + curInputData[0]);
    }

    private boolean isUseDpad1() {
        return emuType != null &&
                (emuType == KeyMap.EmuMap.PSP);
    }

    private boolean skipAnalogProcess() {
        return emuType != null &&
                (emuType == KeyMap.EmuMap.WSC);
    }

    private void processButtons(KeyEvent event, KeyMapEvent keyMapEvent) {
        if (keyMapEvent == null) {
            return;
        }

        int state = 0;

        if (!TextUtils.isEmpty(keyMapEvent.getEmuKey()) && mappedKeyValue != null && mappedKeyValue.contains(keyMapEvent.getEmuKey())) {
            state = Integer.parseInt(keyMapEvent.getEmuKey());
        }

        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            curInputData[0] |= state;
        } else {
            curInputData[0] &= ~state;
        }

//        Log.e("process", "current=" + state + " combine=" + curInputData[0]);
    }
    //endregion

    //region HID输入监听
    public boolean dispatchKeyEvent(KeyEvent event) {
        boolean consumed = false;
        if (mControllerMonitor != null) {
            consumed |= mControllerMonitor.dispatchKeyEvent(event);
        }
        return consumed;
    }

    public boolean dispatchGenericMotionEvent(MotionEvent event) {
        boolean consumed = false;
        if (mControllerMonitor != null) {
            consumed |= mControllerMonitor.dispatchGenericMotionEvent(event);
        }
        return consumed;
    }
    //endregion

    //region SPP输入监听
    ControllerMonitor mControllerMonitor;

    void startMonitor() {
        try {
            //event listener
            if (mControllerMonitor == null) {
                mControllerMonitor = new ControllerMonitor(mContext);
                mControllerMonitor.setListener(createControllerListener(), new Handler());

                //connect listener
                setControllerDeviceListener();
            }

            if (mControllerMonitor != null) {
                mControllerMonitor.startMonitor();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    void stopMonitor() {
        if (mControllerMonitor != null) {
            mControllerMonitor.stopMonitor();
        }
    }

    //region ControllerDeviceListener device connect state
    void setControllerDeviceListener() {
        final Context ctx = mContext;
        if (mControllerMonitor != null) {
            //connect listener
            mControllerMonitor.setControllerDeviceListener(new ControllerManager.ControllerDeviceListener() {
                @Override
                public void onControllerDeviceAdded(ControllerManager.ControllerDevice device) {
                    int count = mControllerMonitor.getControllerDeviceCount();
//                    log("onControllerDeviceAdded", "count=" + count);
                    if (count == 1) {
//                        log("all devices are connected", "手柄已连接");
                        handleChanged(1, mControllerMonitor.getControllerDevices());
                    }
                }

                @Override
                public void onControllerDeviceRemoved(ControllerManager.ControllerDevice device) {
                    int count = mControllerMonitor.getControllerDeviceCount();
//                    log("onControllerDeviceRemoved", "count=" + count);
                    if (count == 0) {
//                        log("all devices are disconnected", "手柄已断开");
                        handleChanged(0, mControllerMonitor.getControllerDevices());
                    }
                }
            });
        }
    }
    //endregion

    //region ControllerListener key-event
    private ControllerListener createControllerListener() {
        return new ControllerListener() {
            @Override
            public void onKeyDown(int keyCode, PadKeyEvent padKeyEvent) {
                onKeyEvent(keyCode, padKeyEvent);
            }

            @Override
            public void onKeyUp(int keyCode, PadKeyEvent padKeyEvent) {
                onKeyEvent(keyCode, padKeyEvent);
            }

            @Override
            public void onKeyPressure(int keyCode, float pressure, PadKeyEvent padKeyEvent) {
                onKeyEvent(keyCode, padKeyEvent);
            }

            @Override
            public void onLeftStick(float x, float y, PadMotionEvent padMotionEvent) {
                onStickEvent(padMotionEvent);
            }

            @Override
            public void onRightStick(float x, float y, PadMotionEvent padMotionEvent) {
                onStickEvent(padMotionEvent);
            }

            @Override
            public void onStateEvent(PadStateEvent padStateEvent) {
//        log(padStateEvent.toString());
            }
        };
    }
    //endregion

    //region 手柄震动
    public void startVibrate() {
        long          duration     = 1200;
        Interpolator  interpolator = new EaseBreathInterpolator();
        ValueAnimator anim         = ValueAnimator.ofInt(0, 0xFF);
        anim.setDuration(duration);
        anim.setInterpolator(interpolator);
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Integer v = (Integer) animation.getAnimatedValue();
                setVibrate(v, v);
            }
        });
        anim.start();
    }

    public void setVibrate(int intensityL, int intensityR) {
        if (mControllerMonitor != null)
            mControllerMonitor.setVibrate(intensityL, intensityR);
    }
    //endregion

    //region UI更新
    float stickLeftLastX;
    float stickLeftLastY;

    boolean onKeyEvent(int keyCode, PadKeyEvent event) {
//        log(event.toString());

        String name = event.getName();
        if (inBlackList(name)) return false;

        boolean ret = false;

        onKeyDispatcher(padEvent2KeyEvent(event));

        return ret;
    }

    boolean onStickEvent(PadMotionEvent event) {
        String name = event.getName();
        if (inBlackList(name)) return false;

        float x = event.getX();
        float y = event.getY();

        if (event.getKeyCode() == BaseEvent.KEYCODE_LEFT_STICK) {
            if (isUseDpad1()) {
                return dispatchStickMotionEvent(event);
            }
            return dispatchStickLeftEvent(event);
        } else if (event.getKeyCode() == BaseEvent.KEYCODE_RIGHT_STICK) {
            return dispatchStickMotionEvent(event);
//            ANALOG_R.draw(x, y);
//            return true;
        }

        return false;
    }

    boolean dispatchStickLeftEvent(PadMotionEvent event) {
        boolean ret = false;
        float   x   = event.getX();
        float   y   = event.getY();

        if (x * stickLeftLastX < 0) {
            onKeyDispatcher(getStickLeftXEvent(stickLeftLastX, KeyEvent.ACTION_UP));
            onKeyDispatcher(getStickLeftXEvent(x, KeyEvent.ACTION_DOWN));
            ret |= true;
        } else if (x * stickLeftLastX == 0) {
            if (x != 0 && stickLeftLastX == 0) {
                onKeyDispatcher(getStickLeftXEvent(x, KeyEvent.ACTION_DOWN));
                ret |= true;
            } else if (x == 0 && stickLeftLastX != 0) {
                onKeyDispatcher(getStickLeftXEvent(stickLeftLastX, KeyEvent.ACTION_UP));
                ret |= true;
            }
        }

        if (y * stickLeftLastY < 0) {
            onKeyDispatcher(getStickLeftYEvent(stickLeftLastY, KeyEvent.ACTION_UP));
            onKeyDispatcher(getStickLeftYEvent(y, KeyEvent.ACTION_DOWN));
            ret |= true;
        } else if (y * stickLeftLastY == 0) {
            if (y != 0 && stickLeftLastY == 0) {
                onKeyDispatcher(getStickLeftYEvent(y, KeyEvent.ACTION_DOWN));
                ret |= true;
            } else if (y == 0 && stickLeftLastY != 0) {
                onKeyDispatcher(getStickLeftYEvent(stickLeftLastY, KeyEvent.ACTION_UP));
                ret |= true;
            }
        }

        stickLeftLastX = x;
        stickLeftLastY = y;
        return ret;
    }

    boolean dispatchStickMotionEvent(PadMotionEvent event) {
        boolean ret     = false;
        int     keyCode = event.getKeyCode();
        float   x       = event.getX();
        float   y       = event.getY();

        try {
            if (mOnKeyMapListener != null) {
                mOnKeyMapListener.onGenericMotionEvent(keyCode, x, y);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }

    KeyEvent getStickLeftXEvent(float direction, int action) {
        int code = direction < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : direction > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_UNKNOWN;
        return new KeyEvent(action, code);
    }

    KeyEvent getStickLeftYEvent(float direction, int action) {
        int code = direction < 0 ? KeyEvent.KEYCODE_DPAD_UP : direction > 0 ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_UNKNOWN;
        return new KeyEvent(action, code);
    }

    KeyEvent padEvent2KeyEvent(PadKeyEvent event) {
        KeyEvent ret = null;

        int keyCode = event.getKeyCode();
        int action = event.getAction();
        float pressure = event.getPressure();
        int deviceId = event.getDeviceId();
        long eventTime = event.getEventTime();
        ret = new KeyEvent(action, keyCode);
        return ret;
    }
    //endregion

    //endregion
}
